package cn.jg.framework.web.controller;

import cn.jg.framework.common.constant.BizCodeConstant;
import cn.jg.framework.common.exception.BadRequestException;
import cn.jg.framework.common.exception.InternalServerErrorException;
import cn.jg.framework.web.controller.response.BaseResponse;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.catalina.connector.ClientAbortException;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.apache.shiro.authz.UnauthenticatedException;
import org.springframework.context.MessageSource;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.context.support.DelegatingMessageSource;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.FieldError;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.util.ContentCachingResponseWrapper;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.validation.ConstraintViolationException;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

/**
 * @author fanfeveryahoo@gmail.com
 * @date 2019/3/14
 */

@Slf4j
@AllArgsConstructor
public abstract class BaseControllerExceptionHandler {

    protected final MessageSource messageSource;
    protected final ObjectMapper objectMapper;

    // @ExceptionHandler(MethodArgumentNotValidException.class)
    // @ResponseStatus(HttpStatus.BAD_REQUEST)
    // @ResponseBody
    // public ResponseEntity<BaseResponse<BadRequestException>> processValidationError(MethodArgumentNotValidException ex) {
    // 	BadRequestException badRequestException = processFieldError(ex.getBindingResult().getFieldErrors());
    // 	return ResponseEntity.badRequest().body(BaseResponse.ok(badRequestException));
    // }

    @ExceptionHandler(MethodArgumentNotValidException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ResponseBody
    public ResponseEntity<BaseResponse> processValidationError(MethodArgumentNotValidException ex) {
        BadRequestException badRequestException = processFieldError(ex.getBindingResult().getFieldErrors());
        // 异常详情记录日志不向前端返回
        try {
            log.error(objectMapper.writeValueAsString(Arrays.stream(ex.getStackTrace())));
        } catch (JsonProcessingException e) {
            e.printStackTrace();
        }
        return ResponseEntity.badRequest().body(BaseResponse.ok(badRequestException));
    }

    // @ExceptionHandler(IOException.class)
    // @ResponseStatus(HttpStatus.SERVICE_UNAVAILABLE)
    // public ResponseEntity<String> exceptionHandler(IOException ex) {
    // 	if (StringUtils.containsIgnoreCase(ExceptionUtils.getRootCauseMessage(ex), "Broken pipe")) {
    // 		return null;
    // 	} else {
    // 		return ResponseEntity.status(HttpStatus.SERVICE_UNAVAILABLE).body(ex.getMessage());
    // 	}
    // }

    @ExceptionHandler(IOException.class)
    @ResponseStatus(HttpStatus.SERVICE_UNAVAILABLE)
    public ResponseEntity<String> exceptionHandler(IOException ex) {
        if (StringUtils.containsIgnoreCase(ExceptionUtils.getRootCauseMessage(ex), "Broken pipe")) {
            return null;
        } else {
            log.error(ex.getMessage());
            return ResponseEntity.status(HttpStatus.SERVICE_UNAVAILABLE).build();
        }
    }

    // XXX: BUG?
    // @ExceptionHandler(HttpRequestMethodNotSupportedException.class)
    // @ResponseStatus(HttpStatus.METHOD_NOT_ALLOWED)
    // @ResponseBody
    // public ResponseEntity<String> httpRequestMethodNotSupportedError(HttpRequestMethodNotSupportedException ex, HttpServletRequest request, ContentCachingResponseWrapper responseWrapper) {
    // 	log.warn("HttpRequestMethodNotSupportedException uri:{}, cause:{}", request.getRequestURI(), ExceptionUtils.getRootCauseMessage(ex));
    // 	return ResponseEntity.status(HttpStatus.METHOD_NOT_ALLOWED).body(ExceptionUtils.getRootCauseMessage(ex));
    // }

    @ExceptionHandler(HttpRequestMethodNotSupportedException.class)
    @ResponseStatus(HttpStatus.METHOD_NOT_ALLOWED)
    @ResponseBody
    public ResponseEntity<String> httpRequestMethodNotSupportedError(HttpRequestMethodNotSupportedException ex, HttpServletRequest request, ContentCachingResponseWrapper responseWrapper) {
        log.warn("HttpRequestMethodNotSupportedException uri:{}, cause:{}", request.getRequestURI(), ExceptionUtils.getRootCauseMessage(ex));
        return ResponseEntity.status(HttpStatus.METHOD_NOT_ALLOWED).build();
    }

    // @ExceptionHandler(Exception.class)
    // @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    // @ResponseBody
    // public ResponseEntity internalServerError(Exception ex, HttpServletRequest request, HttpServletResponse response) {
    // 	if (ex instanceof ClientAbortException) {
    // 		return ResponseEntity.noContent().build();
    // 	}
    // 	if (ex instanceof UnauthenticatedException || ex instanceof org.apache.shiro.authz.UnauthorizedException) {
    // 		return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
    // 	}
    // 	if (ex instanceof BadRequestException) {
    // 		try {
    // 			log.warn("BadRequestException cause:{}", objectMapper.writeValueAsString(Arrays.stream(ex.getStackTrace())
    // 					.filter(i -> i.getClassName().startsWith("com.github.fanfever.fever.") || i.getClassName().startsWith("cn.udesk."))
    // 					.filter(i -> !"<generated>".equals(i.getFileName()))
    // 					.collect(Collectors.toList())));
    // 		} catch (JsonProcessingException e) {
    // 			//
    // 		}
    // 		return ResponseEntity.badRequest().body(BaseResponse.ok(convertMessage((BadRequestException) ex)));
    // 	}
    // 	if (ex instanceof InternalServerErrorException) {
    // 		return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(BaseResponse.ok(ex));
    // 	}
    // 	if (ex instanceof ConstraintViolationException) {
    // 		log.warn("ConstraintViolationException");
    // 		return ResponseEntity.status(HttpStatus.METHOD_NOT_ALLOWED).body(ExceptionUtils.getRootCauseMessage(ex));
    // 	}
    // 	if (ex instanceof HttpRequestMethodNotSupportedException) {
    // 		log.warn("HttpRequestMethodNotSupportedException uri:{}, cause:{}", request.getRequestURI(), ExceptionUtils.getRootCauseMessage(ex));
    // 		return ResponseEntity.status(HttpStatus.METHOD_NOT_ALLOWED).body(ExceptionUtils.getRootCauseMessage(ex));
    // 	}
    // 	return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(BaseResponse.ok(new InternalServerErrorException(BizCodeConstant.UNKNOWN_ERROR, ex)));
    // }

    @ExceptionHandler(Exception.class)
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    @ResponseBody
    public ResponseEntity internalServerError(Exception ex, HttpServletRequest request, HttpServletResponse response) {
        if (ex instanceof ClientAbortException) {
            return ResponseEntity.noContent().build();
        }
        if (ex instanceof UnauthenticatedException || ex instanceof org.apache.shiro.authz.UnauthorizedException) {
            return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
        }
        if (ex instanceof BadRequestException) {
            log.warn("BadRequestException occurs.", ex);
            return ResponseEntity.badRequest().build();
        }
        if (ex instanceof InternalServerErrorException) {
            log.warn("InternalServerErrorException occurs.", ex);
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
        }
        if (ex instanceof ConstraintViolationException) {
            log.warn("ConstraintViolationException occurs.", ex);
            return ResponseEntity.status(HttpStatus.METHOD_NOT_ALLOWED).build();
        }
        if (ex instanceof HttpRequestMethodNotSupportedException) {
            log.warn("HttpRequestMethodNotSupportedException occurs.", ex);
            return ResponseEntity.status(HttpStatus.METHOD_NOT_ALLOWED).build();
        }
        log.warn("Exception occurs", ex);

        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
    }


    private BadRequestException processFieldError(List<FieldError> fieldErrorList) {
        if (CollectionUtils.isEmpty(fieldErrorList)) {
            return null;
        }
        List<String> messageList = null;
        if (messageSource instanceof DelegatingMessageSource) {
            DelegatingMessageSource source = (DelegatingMessageSource) this.messageSource;
            MessageSource parentMessageSource = source.getParentMessageSource();
            if (parentMessageSource == null) {
                messageList = fieldErrorList.stream().map(FieldError::getDefaultMessage).collect(Collectors.toList());
            }
        } else {
            messageList = fieldErrorList.stream()
                    .map(i -> messageSource.getMessage(Objects.requireNonNull(i.getDefaultMessage()), null, LocaleContextHolder.getLocale()))
                    .collect(Collectors.toList());
        }
        return new BadRequestException(BizCodeConstant.BAD_REQUEST, messageList);
    }

    private BadRequestException convertMessage(BadRequestException badRequestException) {
        badRequestException.setMessageList(convertMessage(badRequestException.getMessageList()));
        return badRequestException;
    }

    protected List<String> convertMessage(List<String> messageList) {
        if (CollectionUtils.isEmpty(messageList)) {
            return messageList;
        }
        return messageList.stream().map(i -> messageSource.getMessage(i, null, LocaleContextHolder.getLocale())).collect(Collectors.toList());
    }

}
